/**
 *    Copyright 2009-2020 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.builder.xml;

import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.CacheRefResolver;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.ResultMapResolver;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Discriminator;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

/**
 * @author Clinton Begin
 * @author Kazuki Shimizu
 */
public class XMLMapperBuilder extends BaseBuilder {

  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;

  @Deprecated
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
    this(reader, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  @Deprecated
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
    this(inputStream, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }

  /*xxx: 解析映射文件的入口*/
  public void parse() {
    /*xxx: 判断是否已经加载过  该映射文件 */
    if (!configuration.isResourceLoaded(resource)) {
      /*xxx: 处理 <mapper> 节点*/
      configurationElement(parser.evalNode("/mapper"));
      /*xxx: 将 resource添加到 Configuration.loadedResources集合中保存，它是 HashSet<String> 类型的集合，其中记录了已经加载过的映射文件*/
      configuration.addLoadedResource(resource);
      /*xxx: 注册 Mapper接口*/
      bindMapperForNamespace();
    }
    /*xxx: 由于解析映射配置文件，是 按照从文件头 到 文件尾 的顺序解析的，  但是在解析过程中，可能会引用定义在该节点之后的，还未解析的节点*/
    /*xxx: 这是会 抛出  IncompleteElementException，将相应的节点进行暂存*/
    /*xxx: 根据类型的不同，mybatis提供了三种 类型进行 后置处理*/

    /*xxx: 处理 configuratioElement方法中，解析失败的 <resultMap> 节点*/
    parsePendingResultMaps();
    /*xxx: 处理 configurationElement 方法中解析失败的 <cache-ref> 节点*/
    parsePendingCacheRefs();
    /*xxx： 处理  configurationElement 方法中 解析失败的 SQL 语句节点 */
    parsePendingStatements();
  }

  public XNode getSqlFragment(String refid) {
    return sqlFragments.get(refid);
  }

  private void configurationElement(XNode context) {
    try {
      /*xxx: 获取 <mapper> 节点的  namespace属性*/
      String namespace = context.getStringAttribute("namespace");
      /*xxx: 如果 namespace 属性为空，则抛出异常*/
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      /*xxx: 设置 MapperBuilderAssistant 的 currentNamespace 字段，记录当前命名空间*/
      builderAssistant.setCurrentNamespace(namespace);

      /*xxx: 解析 <cache-ref> 节点*/
      cacheRefElement(context.evalNode("cache-ref"));
      /*xxx： 解析 <cache> 节点*/
      cacheElement(context.evalNode("cache"));
      /*xxx: 解析  <parameterMap> 节点,已废弃，不推荐使用*/
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      /*xxx: 解析 <resultMap> 节点*/
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      /*xxx: 解析 <sql> 节点*/
      sqlElement(context.evalNodes("/mapper/sql"));
      /*xxx: 解析  <select>,<insert>,<update>, <delete> 等sql节点*/
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

  private void parsePendingResultMaps() {
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {
      Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolve();
          iter.remove();
        } catch (IncompleteElementException e) {
          // ResultMap is still missing a resource...
        }
      }
    }
  }

  private void parsePendingCacheRefs() {
    Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
    synchronized (incompleteCacheRefs) {
      Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolveCacheRef();
          iter.remove();
        } catch (IncompleteElementException e) {
          // Cache ref is still missing a resource...
        }
      }
    }
  }

  private void parsePendingStatements() {
    /*xxx: 获取  Configuration.incompleteStatements集合*/
    Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
    /*xxx: 加锁 同步*/
    synchronized (incompleteStatements) {
      /*xxx: 遍历 incompleteStatements 集合*/
      Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
      while (iter.hasNext()) {
        try {
          /*xxx: 重新 解析 sql 语句节点*/
          iter.next().parseStatementNode();
          /*xxx: 移除 XMLStatementBuilder对象*/
          iter.remove();
        } catch (IncompleteElementException e) {
          // Statement is still missing a resource...
          /*xxx: 如果依然无法解析，则忽略该节点*/
        }
      }
    }
  }

  /*xxx: 多个 命名空间公用一个 二级缓存时，即通过  <cache-ref> 节点配置*/
  private void cacheRefElement(XNode context) {
    if (context != null) {
      /*xxx: 将当前Mapper配置文件的  namespace 与 被引用的 Cache 所在的 namespace之间的对应关系，记录到 Configuration.cacheRefMap集合中*/
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      /*xxx: 创建  CacheRefResolver对象*/
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
        /*xxx: 解析 Cache 引用， 该过程主要是 设置  MapperBuilderAssistant中的  currentCache 和 unresolvedCacheRef字段*/
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        /*xxx: 如 解析过程出现异常， 则添加到  Configuration.incompleteCacheRefs集合，稍后再解析*/
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }

  /*xxx: mybatis 拥有非常强大的二级缓存，默认情况下没有开启*/
  /*xxx: 如果 需要为 某个命名空间，开启二级缓存功能，需要在相应配置文件中，添加 <cache> 节点*/
  private void cacheElement(XNode context) {
    if (context != null) {
      /*xxx: 获取 <cache> 节点的type属性，默认值是  PERPETUAL,本质时就是个 HashMap*/
      String type = context.getStringAttribute("type", "PERPETUAL");
      /*xxx: 查找 type属性对应的Cache接口实现*/
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      /*xxx: 获取 <cache>节点的 eviction属性，默认是 LRU*/
      String eviction = context.getStringAttribute("eviction", "LRU");
      /*xxx: 解析 eviction 属性 指定的  Cache 装饰器类型*/
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      /*xxx: 获取 <cache> 节点的  flushInterval 属性，默认值是  null*/
      Long flushInterval = context.getLongAttribute("flushInterval");
      /*xxx: 获取 <cache> 节点的 size 属性,默认值是 null*/
      Integer size = context.getIntAttribute("size");
      /*xxx: 获取 <cache> 节点的 readOnly属性，默认值是 false*/
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      /*xxx: 获取 <cache>节点的  blocking 属性，默认值是 false*/
      boolean blocking = context.getBooleanAttribute("blocking", false);
      /*xxx: 获取 <cache> 节点下的子节点，将用于初始化二级缓存*/
      Properties props = context.getChildrenAsProperties();
      /*xxx: 通过 MapperBuilderAssistant创建 Cache对象，并添加到 Configuration.caches集合中保存*/
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

  private void parameterMapElement(List<XNode> list) {
    for (XNode parameterMapNode : list) {
      String id = parameterMapNode.getStringAttribute("id");
      String type = parameterMapNode.getStringAttribute("type");
      Class<?> parameterClass = resolveClass(type);
      List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
      List<ParameterMapping> parameterMappings = new ArrayList<>();
      for (XNode parameterNode : parameterNodes) {
        String property = parameterNode.getStringAttribute("property");
        String javaType = parameterNode.getStringAttribute("javaType");
        String jdbcType = parameterNode.getStringAttribute("jdbcType");
        String resultMap = parameterNode.getStringAttribute("resultMap");
        String mode = parameterNode.getStringAttribute("mode");
        String typeHandler = parameterNode.getStringAttribute("typeHandler");
        Integer numericScale = parameterNode.getIntAttribute("numericScale");
        ParameterMode modeEnum = resolveParameterMode(mode);
        Class<?> javaTypeClass = resolveClass(javaType);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
        ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
        parameterMappings.add(parameterMapping);
      }
      builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
  }

  private void resultMapElements(List<XNode> list) {
    for (XNode resultMapNode : list) {
      try {
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }

  private ResultMap resultMapElement(XNode resultMapNode) {
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
  }

  /*xxx: 通过 resultMapElements 方法 解析 映射配置文件中的  全部  <resultMap> 节点*/
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    /*xxx: 获取 <resultMap> 节点的  type 属性，表示 结果集 将被 映射成 type 指定类型的 对象*/
    /*xxx: 它本身有一个 默认值梯队，优先级为 :  type > ofType > resultType > javaType*/
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    /*xxx: 解析  type类型*/
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    /*xxx: 该集合用于 记录解析的结果 */
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    /*xxx: 处理 <resultMap>的子节点*/
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        /*xxx: 处理<constructor> 节点*/
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        /*xxx: 处理 <discriminator> 节点*/
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        /*xxx:处理 <id>,<result>,<association>,<collection> 等节点*/
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          /*xxx: 如果是<id>节点，则向 flags 集合中，添加 ResultFlag.Id*/
          flags.add(ResultFlag.ID);
        }
        /*xxx: 创建 ResultMapping 对象，并添加到  resultMappings 集合中保存*/
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    /*xxx: 获取 <resultMap> 的id 属性，默认值 会 拼装 所有父节点的  id 或 value 或 Property 属性值*/
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    /*xxx: 获取 <resultMap> 节点的  extends 属性，该属性指定了  该 <resultMap> 节点的 继承关系*/
    String extend = resultMapNode.getStringAttribute("extends");
    /*xxx: 读取  <resultMap> 节点的 autoMapping 属性，将该属性设置为  true ，则启动自动映射功能*/
    /*xxx: 即 自动查找 与 列名同名的属性名，并调用  setter 方法*/
    /*xxx: 设置为 false后，则需要在  <resultMap> 节点内 明确注明 映射关系，才会调用对应的 setter方法*/
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      /*xxx: 创建 ResultMap 对象，并添加到  Configuration.resultMaps 集合中，该集合是 StrictMap 类型*/
      return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

  protected Class<?> inheritEnclosingType(XNode resultMapNode, Class<?> enclosingType) {
    if ("association".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
      String property = resultMapNode.getStringAttribute("property");
      if (property != null && enclosingType != null) {
        MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
        return metaResultType.getSetterType(property);
      }
    } else if ("case".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
      return enclosingType;
    }
    return null;
  }

  private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
    /*xxx: 获取 <constructor> 节点的子节点*/
    List<XNode> argChildren = resultChild.getChildren();
    for (XNode argChild : argChildren) {
      List<ResultFlag> flags = new ArrayList<>();
      /*xxx: 添加CONSTRUCTOR 标识*/
      flags.add(ResultFlag.CONSTRUCTOR);
      if ("idArg".equals(argChild.getName())) {
        /*xxx: 对于 <idArg> 节点，添加ID标志 */
        flags.add(ResultFlag.ID);
      }
      /*xxx: 创建 ResultMapping 对象，并添加到 resultMappings 集合中*/
      resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
  }

  private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) {
    /*xxx: 获取column,javaType,jdbcType,typeHandler属性*/
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String typeHandler = context.getStringAttribute("typeHandler");
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    /*xxx: 处理 <discriminator> 节点的子节点 */
    Map<String, String> discriminatorMap = new HashMap<>();
    for (XNode caseChild : context.getChildren()) {
      String value = caseChild.getStringAttribute("value");
      /*xxx: 调用 processNestedResultMappings 方法 创建嵌套的ResultMap对象*/
      String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings, resultType));
      /*xxx: 记录该列值 与 对应选择的 ResultMap 的Id */
      discriminatorMap.put(value, resultMap);
    }
    /*xxx: 创建 Discriminator 对象*/
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
  }

  private void sqlElement(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    /*xxx: 遍历 <sql> 节点*/
    for (XNode context : list) {
      /*xxx: 获取 databaseId属性*/
      String databaseId = context.getStringAttribute("databaseId");
      /*xxx: 获取 id属性*/
      String id = context.getStringAttribute("id");
      /*xxx: 为  id 添加命名空间*/
      id = builderAssistant.applyCurrentNamespace(id, false);
      /*xxx: 检测 <sql> 的 databaseId 与 当前 Configuration 中记录的 databaseId 是否一致*/
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        /*xxx: 记录到  sqlFragments中保存*/
        /*xxx: 在构造函数中，该函数指向了 Configuration.sqlFragments 集合*/
        sqlFragments.put(id, context);
      }
    }
  }

  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
      return requiredDatabaseId.equals(databaseId);
    }
    if (databaseId != null) {
      return false;
    }
    if (!this.sqlFragments.containsKey(id)) {
      return true;
    }
    // skip this fragment if there is a previous one with a not null databaseId
    XNode context = this.sqlFragments.get(id);
    return context.getStringAttribute("databaseId") == null;
  }

  private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
    String property;
    /*xxx: 获取节点的相关属性值 : name，property(二选一), column,javaType,jdbcTpe,select,resultMap
    *     ,notNullColumn,columnPrefix,typeHandler,resultSet,foreignColumn*/
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
      property = context.getStringAttribute("property");
    }
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    /*xxx: 如果未指定 <association> 节点的  resultMap属性，则是 匿名的嵌套映射，
        需要通过 processNestedResultMappings  方法 解析该匿名的嵌套映射*/
    String nestedResultMap = context.getStringAttribute("resultMap", () ->
        processNestedResultMappings(context, Collections.emptyList(), resultType));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    /*xxx: 获取是否懒加载配置*/
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    Class<?> javaTypeClass = resolveClass(javaType);
    /*xxx: 将相应属性，转换为 枚举*/
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    /*xxx: 创建ResultMapping对象*/
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }

  private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) {
    /*xxx： 只会处理 <association>,<collection>,和 <case> 三种节点*/
    if (Arrays.asList("association", "collection", "case").contains(context.getName())
        && context.getStringAttribute("select") == null) {
      validateCollection(context, enclosingType);
      /*xxx: 创建 ResultMap 对象，并添加到  Configuration.resultMaps集合中*/
      ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
      return resultMap.getId();
    }
    /*xxx: 指定了  select 属性后，则不会生成嵌套节点*/
    return null;
  }

  protected void validateCollection(XNode context, Class<?> enclosingType) {
    if ("collection".equals(context.getName()) && context.getStringAttribute("resultMap") == null
        && context.getStringAttribute("javaType") == null) {
      MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
      String property = context.getStringAttribute("property");
      if (!metaResultType.hasSetter(property)) {
        throw new BuilderException(
            "Ambiguous collection type for property '" + property + "'. You must specify 'javaType' or 'resultMap'.");
      }
    }
  }

  /*xxx: 该方法  完成了 映射配置文件 与 对应 Mapper接口的绑定*/
  private void bindMapperForNamespace() {
    /*xxx: 获取 应黑色配置文件的命名空间*/
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        /*xxx: 解析命名空间对应的类型*/
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      /*xxx: 检测是否已经加载了 boundType接口*/
      if (boundType != null && !configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        /*xxx: 追加 namespace前缀，并添加到  Configuration.loadedReources集合中保存*/
        configuration.addLoadedResource("namespace:" + namespace);
        /*xxx: 调用 MapperRegistry.addMapper方法，注册 boundType接口*/
        configuration.addMapper(boundType);
      }
    }
  }

}
